/** \file gr_tri_mesh.h A triangle mesh for storage of large meshes. 
    Using the Campagna '98 "Directed Edges - A Scalable Representation for Triangle Meshes"
    Directed Edge data structure.

    Since this is used also for rendering support is added for auxiliary channels of vertex data.

*/

#pragma once

#include "gr_numeric_types.h"
#include "gr_aligned_allocator.h"
#include "gr_streamsignature.h"
#include "gr_streamdata.h"

#include "gr_tokens.h" // We use default names GR_POSITION and GR_NORMAL to identiy position and normal channels
#include "gr_shims.h"
#include "gr_algorithms.h"

#include<vector>
#include<functional>
#include<cstdint>
#include<cstring>
#include<exception>
#include<memory>
#include<unordered_map>

namespace gr {

/** Namespace container for common mesh properties*/
class Mesh
{
    public:
    enum AttributeHost {VERTEX, EDGE, FACE};
    enum AttributeType {MESH_VEC4, MESH_VEC3, MESH_FLOAT}; // TODO ->, MESH_INT32, MESH_STRING};

    typedef AlignedArray<vec4> Vec4Container; // need a 16-byte aligned container
    typedef std::vector<vec3>  Vec3Container;
    typedef std::vector<float> FloatContainer;
    //typedef std::vector<int32_t>     Int32Container;
    //typedef std::vector<std::string> StringContainer;

    /** Use attributes to attach properties to mesh. 
     * Specialize this template type
     * */
    template<class M>
    struct AttributeHandle
    {
        typedef std::function<void(AttributeHandle<M>&, M&)> UpdateFun;

        std::string    name;
        AttributeHost  host;
        AttributeType  type;
        UpdateFun updateFun;

        void*  container_;
        size_t size;
        bool   attached;

        Vec4Container* getAttributeArrayVec4()
        {
            Vec4Container* arr = nullptr;
            if(type == MESH_VEC4) arr = container_;
            else throw std::runtime_error("Mesh::Attribute incorrect data type access: not Vec4.");
            return arr;
         }

        Vec3Container* getAttributeArrayVec3()
        {
            Vec3Container* arr = nullptr;
            if(type == MESH_VEC3) arr = container_; 
            else throw std::runtime_error("Mesh::Attribute incorrect data type access: not Vec3.");
            return arr;
        }

        FloatContainer* getAttributeArrayFloat()
        {
            FloatContainer* arr = nullptr;
            if(attribute.type == MESH_VEC3) arr = container_;
            else throw std::runtime_error("Mesh::Attribute incorrect data type access: not float.");
            return arr;
        }

        static void emptyUpdate(AttributeHandle<M>& attribute, M& mesh)
        {
            return;
        }


        AttributeHandle(const char* aname, AttributeHost ahost, AttributeType atype, UpdateFun afun)
            :name(aname), host(ahost), type(atype),container_(nullptr), attached(false), updateFun(afun), size(0)
        {}

        AttributeHandle(const char* aname, AttributeHost ahost, AttributeType atype)
            :name(aname), host(ahost), type(atype),container_(nullptr), attached(false), updateFun(emptyUpdate), size(0)
        {}

        // TODO: make private
        void pushAttributeVec4()
        {
           if(auto attr_array = getAttributeArrayVec4())
           {
               attr_array.push_back(vec4(1.0f, 0.0f, 0.0f, 1.0f));
           }
        }

        void pushAttributeVec3()
        {
           if(auto attr_array = getAttributeArrayVec3())
           {
               attr_array.push_back(vec3(1.0f, 0.0f, 0.0f));
           }
        }

        void pushAttributeFloat()
        {
           if(auto attr_array = getAttributeArrayFloat())
           {
               attr_array.push_back(0.0f);
           }
        }

        void pushAttribute()
        {
            switch(type)
            {
                case MESH_FLOAT: pushAttributeFloat(); break;
                case MESH_VEC3: pushAttributeVec3(); break;
                case MESH_VEC4: pushAttributeVec4(); break;
            }
            size += 1;
        }

        void resizeTo(size_t s)
        {
            if(s > size)
            {
                size_t count = s - size;
                switch(type)
                {
                    case MESH_FLOAT: for(size_t i = 0; i < count; ++i) pushAttributeFloat(); break;
                    case MESH_VEC3:  for(size_t i = 0; i < count; ++i) pushAttributeVec3(); break;
                    case MESH_VEC4:  for(size_t i = 0; i < count; ++i) pushAttributeVec4(); break;
                }
            }
        }

        void resizeToLinkedElementCount(M& mesh)
        {
            switch(host)
            {
                case VERTEX: resizeTo(mesh.vertexCount()); break;
                case FACE:   resizeTo(mesh.faceCount()); break;
                case EDGE:   resizeTo(mesh.edgeCount()); break;
            }
        }

        void attach(){
            switch(type)
            {
                case MESH_VEC4:container_ = (void*) new Vec4Container(); break;
                case MESH_VEC3:container_ = (void*) new Vec3Container(); break;
                case MESH_FLOAT:container_ =(void*) new FloatContainer(); break;
                default: break;
            }
            attached = true;
            if(! updateFun) updateFun = emptyUpdate;
        }

        ~AttributeHandle()
        {
            if(container_)
            {
                switch(type)
                {
                    case MESH_VEC4: delete ((Vec4Container*)container_); break;
                    case MESH_VEC3: delete ((Vec3Container*)container_); break;
                    case MESH_FLOAT: delete ((FloatContainer*)container_); break;
                    default: break;
                }
            }
        }

        void updateAttributeValues(MESH& mesh)
        {
            updateFun(*this, mesh);
        }

        void* container(){return container_;}
        bool operator<(const AttributeHandle& a){return strcmp(name.c_str(), a.name.c_str()) < 0;}
    };



};
/**
  Directed Edge Mesh:
vertex: position, outgoing_edge_index
edge: start_vertex_index, facing_edge_index
Face f_i: e_i:valence * f_i + 0, + 1, + 2 , ( + 3)

Face ordering: counter-clockwise.
For triangles or quads (face valence of three or four)
*/
template<int VALENCE>
class ConstantValenceMesh
{
public:

    typedef std::function<void(AttributeHandle, TriMesh&)> UpdateFunction;

    typedef int32_t edge_index;
    typedef int32_t vertex_index;
    // Now we can fit two face indices to a single 64-btye
    /** Total size must be aligned to 16 bytes to use sse through Eigen effectively.
     * Now size is 3 * 4 bytes + 4 bytes = 16 bytes. */

    static const edge_index_nil = -1; // Signifies that there is no edge
    // values smaller than this are an index to the auxiliary list.

    /** Check if the index signifies data is stored in the auxiliary table. */
    inline static edge_index auxiliaryIndex(edge_index i){return -(i) - 1;}
    inline static bool       isAuxiliary(edge_index i){return i < nil;}

    /** Position information is married to the out edge data. */
    struct Vertex
    {
        vec3       position;
        edge_index edgeOut;

        Vertex(float x, float y, float z)
        {
            position = vec3(x, y, z);
            edgeOut = edge_index_nil;
        }
    };

    struct Edge
    {
        vertex_index end;
        edge_index   opposite;

        Edge(vertex_index endi, edge_index oppositei = edge_index_nil):end(endi), opposite(oppositei){}
    };

    typedef std::vector<Vertex>  VertexArray;
    typedef std::vector<Edge>    EdgeArray;

    VertexArray vertices_;
    EdgeArray   edges_;

    Vertex& vertex(vertex_index i){return vertices_[i];}
    Edge& edge(edge_index i){return edges_[i];}

    /** An auxiliary edge table contains edge data for non-manifold vertices. Entries
     *  are formatted so first entry n contains the number of edges and the  next n entries
     *  are edge indices.
     *  |3 e_m e_n e_o | 4 e_a e_b e_c e_d| ...
     */
    std::vector<edge_index> auxiliaries_; 

    typedef Mesh::AttributeHandle<ConstantFaceValenceMesh<VALENCE>> Attribute;
    typedef std::set<Attribute>                               AttributeContainer;

    AttributeContainer attributes_;
    std::string        positionName_; //> Position name is handled separately due to implementation

    /** TriMesh constructor */
    ConstantValenceMesh(const AttributeContainer& attributes):positionName_(GR_POSITION)
    {
        // First assert that there are no duplicate names
        if(find(attributes, positionName_))
        {
            assert(!"Duplicate names");
            throw  std::runtime_error("Tri Mesh: duplicate names in assignment.");
        }

        attributes_ = attributes;

        auto registerAttribute = [&](const AttributeV& attr)
        {
            attr.attach();
        };

        forEach(attributes_, registerAttribute);
    }

    size_t vertexCount(){return vertices_.size();}
    size_t edgeCount(){return edges_.size();}
    size_t faceCount(){return edges_.size() / VALENCE;}

    void resizeAttributeArrays()
    {
        size_t size = vertices_.size();
        forEach(attributes_, std::mem_fun_ref(std::bind(&Attribute.resizeTo, size)));
    }
    
    void updateAttributeValues()
    {
        forEach(attributes_, std::mem_fun_ref(&Attribute.updateAttributeValues));
    }

    void updateAttributes()
    {
        resizeAttributeArrays();
        updateAttributeValues();
    } 

    struct VertexHandle
    {
        vertex_index index;
    };

    /** Faces are stored implicitly as edges.*/
    struct FaceHandle
    {
        edge_index index;
    };

    typedef std::vector<VertexHandle> VertexHandleArray;
    VertexHandle addVertex(float x, float y, float z)
    {
        VertexHandle r;
        r.index = size(vertices_);
        vertices_.push_back(Vertex(x, y, z));
        return r;
    }

    edge_index addEdge(vertex_index v)
    {
        edge_index opposingEdge = vertices_[v].edgeOut;
        edges_.push_back(Edge(v, opposingEdge));
        return lastIndex(edges_);
    }

    FaceHandle addFaces(VertexHanldeArray& vertexHandles)
    {
        int newFaceCount = size(vertices) / VALENCE;

        auto pushEdge = [&](vertex_index f)
        {
            vertex_index last = f + VALENCE;
            for(auto v = f; v < last; ++v)
            {
                auto nextVertex = (v + 1)%VALENCE;
                auto edge_i = addEdge(nextVertex);
                vertices[v].edgeOut = edge_i;
            }
        };

        for(int f = 0; f < newFaceCount; ++f)
        {
            pushEdge(f);
        }
    }

    /** Return a const reference to the identifier of position channel*/
    const std::string& positionName() const {return positionName_;}
    /** Explicit setter for position name.*/
    void setPositionName(const char* str){positionName_ = str;}

    // Get attributes bound to hostType element (vertex, edge or face)
    std::vector<const Attribute*> getAttributes(AttributeHost hostType)
    {
        std::vector<const Attribute*>  attributes;
        auto insertAttr = [&](Attribute& attr){
            if(attr.host == hostType) add(attributes,attr);
        };
        forEach(attributes_, insertAttr);
        return attributes;
    }

};

typedef ConstantValenceMesh<3> TriangleMesh;
typedef ConstantValenceMesh<4> QuadMesh;

/* TODO: Move functions below to a class meshadapter or something.*/
template<class M>
StreamSignature getStreamSignature(M& mesh)
{
    StreamSignature signature;
}

void triangleMeshToStreamData(TriangleMesh mesh, StreamData vertices, StreamGeometryIndexData indices)
{
    // Streamdata defines a stream signature. It attempts to extract the channels defined 
    // in the signature from the mesh. If all channels are not found an exception is raised.

    // Match stream signature

    // Load vertices to vertex array
    // Load indices to index array
}

}
